//===--- DiagnosticsTests.cpp ------------------------------------*- C++-*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "../clang-tidy/ClangTidyOptions.h"
#include "Annotations.h"
#include "Config.h"
#include "Diagnostics.h"
#include "Feature.h"
#include "FeatureModule.h"
#include "ParsedAST.h"
#include "Protocol.h"
#include "TestFS.h"
#include "TestIndex.h"
#include "TestTU.h"
#include "TidyProvider.h"
#include "index/MemIndex.h"
#include "index/Ref.h"
#include "index/Relation.h"
#include "index/Symbol.h"
#include "support/Context.h"
#include "support/Path.h"
#include "clang/AST/Decl.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/Specifiers.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/ScopedPrinter.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

namespace clang {
namespace clangd {
namespace {

using ::testing::_;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Not;
using ::testing::Pair;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;

::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher) {
  return Field(&Diag::Fixes, ElementsAre(FixMatcher));
}

::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher1,
                                         ::testing::Matcher<Fix> FixMatcher2) {
  return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2));
}

::testing::Matcher<const Diag &> withID(unsigned ID) {
  return Field(&Diag::ID, ID);
}
::testing::Matcher<const Diag &>
withNote(::testing::Matcher<Note> NoteMatcher) {
  return Field(&Diag::Notes, ElementsAre(NoteMatcher));
}

::testing::Matcher<const Diag &>
withNote(::testing::Matcher<Note> NoteMatcher1,
         ::testing::Matcher<Note> NoteMatcher2) {
  return Field(&Diag::Notes, UnorderedElementsAre(NoteMatcher1, NoteMatcher2));
}

::testing::Matcher<const Diag &>
withTag(::testing::Matcher<DiagnosticTag> TagMatcher) {
  return Field(&Diag::Tags, Contains(TagMatcher));
}

MATCHER_P(hasRange, Range, "") { return arg.Range == Range; }

MATCHER_P2(Diag, Range, Message,
           "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
  return arg.Range == Range && arg.Message == Message;
}

MATCHER_P3(Fix, Range, Replacement, Message,
           "Fix " + llvm::to_string(Range) + " => " +
               ::testing::PrintToString(Replacement) + " = [" + Message + "]") {
  return arg.Message == Message && arg.Edits.size() == 1 &&
         arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
}

MATCHER_P(fixMessage, Message, "") { return arg.Message == Message; }

MATCHER_P(equalToLSPDiag, LSPDiag,
          "LSP diagnostic " + llvm::to_string(LSPDiag)) {
  if (toJSON(arg) != toJSON(LSPDiag)) {
    *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",
                                      toJSON(LSPDiag), toJSON(arg))
                            .str();
    return false;
  }
  return true;
}

MATCHER_P(diagSource, S, "") { return arg.Source == S; }
MATCHER_P(diagName, N, "") { return arg.Name == N; }
MATCHER_P(diagSeverity, S, "") { return arg.Severity == S; }

MATCHER_P(equalToFix, Fix, "LSP fix " + llvm::to_string(Fix)) {
  if (arg.Message != Fix.Message)
    return false;
  if (arg.Edits.size() != Fix.Edits.size())
    return false;
  for (std::size_t I = 0; I < arg.Edits.size(); ++I) {
    if (arg.Edits[I].range != Fix.Edits[I].range ||
        arg.Edits[I].newText != Fix.Edits[I].newText)
      return false;
  }
  return true;
}

// Helper function to make tests shorter.
Position pos(int Line, int Character) {
  Position Res;
  Res.line = Line;
  Res.character = Character;
  return Res;
}

// Normally returns the provided diagnostics matcher.
// If clang-tidy checks are not linked in, returns a matcher for no diagnostics!
// This is intended for tests where the diagnostics come from clang-tidy checks.
// We don't #ifdef each individual test as it's intrusive and we want to ensure
// that as much of the test is still compiled an run as possible.
::testing::Matcher<std::vector<clangd::Diag>>
ifTidyChecks(::testing::Matcher<std::vector<clangd::Diag>> M) {
  if (!CLANGD_TIDY_CHECKS)
    return IsEmpty();
  return M;
}

TEST(DiagnosticsTest, DiagnosticRanges) {
  // Check we report correct ranges, including various edge-cases.
  Annotations Test(R"cpp(
    // error-ok
    #define ID(X) X
    namespace test{};
    void $decl[[foo]]();
    int main() {
      struct Container { int* begin(); int* end(); } *container;
      for (auto i : $insertstar[[]]$range[[container]]) {
      }

      $typo[[go\
o]]();
      foo()$semicolon[[]]//with comments
      $unk[[unknown]]();
      double $type[[bar]] = "foo";
      struct Foo { int x; }; Foo a;
      a.$nomember[[y]];
      test::$nomembernamespace[[test]];
      $macro[[ID($macroarg[[fod]])]]();
    }
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(
          // Make sure the whole token is highlighted.
          AllOf(Diag(Test.range("range"),
                     "invalid range expression of type 'struct Container *'; "
                     "did you mean to dereference it with '*'?"),
                withFix(Fix(Test.range("insertstar"), "*", "insert '*'"))),
          // This range spans lines.
          AllOf(Diag(Test.range("typo"),
                     "use of undeclared identifier 'goo'; did you mean 'foo'?"),
                diagSource(Diag::Clang), diagName("undeclared_var_use_suggest"),
                withFix(
                    Fix(Test.range("typo"), "foo", "change 'go\\…' to 'foo'")),
                // This is a pretty normal range.
                withNote(Diag(Test.range("decl"), "'foo' declared here"))),
          // This range is zero-width and insertion. Therefore make sure we are
          // not expanding it into other tokens. Since we are not going to
          // replace those.
          AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"),
                withFix(Fix(Test.range("semicolon"), ";", "insert ';'"))),
          // This range isn't provided by clang, we expand to the token.
          Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"),
          Diag(Test.range("type"),
               "cannot initialize a variable of type 'double' with an lvalue "
               "of type 'const char[4]'"),
          Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"),
          Diag(Test.range("nomembernamespace"),
               "no member named 'test' in namespace 'test'"),
          AllOf(Diag(Test.range("macro"),
                     "use of undeclared identifier 'fod'; did you mean 'foo'?"),
                withFix(Fix(Test.range("macroarg"), "foo",
                            "change 'fod' to 'foo'")))));
}

// Verify that the -Wswitch case-not-covered diagnostic range covers the
// whole expression. This is important because the "populate-switch" tweak
// fires for the full expression range (see tweaks/PopulateSwitchTests.cpp).
// The quickfix flow only works end-to-end if the tweak can be triggered on
// the diagnostic's range.
TEST(DiagnosticsTest, WSwitch) {
  Annotations Test(R"cpp(
    enum A { X };
    struct B { A a; };
    void foo(B b) {
      switch ([[b.a]]) {}
    }
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  TU.ExtraArgs = {"-Wswitch"};
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(Diag(Test.range(),
                               "enumeration value 'X' not handled in switch")));
}

TEST(DiagnosticsTest, FlagsMatter) {
  Annotations Test("[[void]] main() {} // error-ok");
  auto TU = TestTU::withCode(Test.code());
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
                                withFix(Fix(Test.range(), "int",
                                            "change 'void' to 'int'")))));
  // Same code built as C gets different diagnostics.
  TU.Filename = "Plain.c";
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(AllOf(
          Diag(Test.range(), "return type of 'main' is not 'int'"),
          withFix(Fix(Test.range(), "int", "change return type to 'int'")))));
}

TEST(DiagnosticsTest, DiagnosticPreamble) {
  Annotations Test(R"cpp(
    #include $[["not-found.h"]] // error-ok
  )cpp");

  auto TU = TestTU::withCode(Test.code());
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(::testing::AllOf(
                  Diag(Test.range(), "'not-found.h' file not found"),
                  diagSource(Diag::Clang), diagName("pp_file_not_found"))));
}

TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {
  Annotations Test(R"cpp(
    float foo = [[0.1f]];
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  // Enable alias clang-tidy checks, these check emits the same diagnostics
  // (except the check name).
  TU.ClangTidyProvider = addTidyChecks("readability-uppercase-literal-suffix,"
                                       "hicpp-uppercase-literal-suffix");
  // Verify that we filter out the duplicated diagnostic message.
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
          Diag(Test.range(),
               "floating point literal has suffix 'f', which is not uppercase"),
          diagSource(Diag::ClangTidy)))));

  Test = Annotations(R"cpp(
    template<typename T>
    void func(T) {
      float f = [[0.3f]];
    }
    void k() {
      func(123);
      func(2.0);
    }
  )cpp");
  TU.Code = std::string(Test.code());
  // The check doesn't handle template instantiations which ends up emitting
  // duplicated messages, verify that we deduplicate them.
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
          Diag(Test.range(),
               "floating point literal has suffix 'f', which is not uppercase"),
          diagSource(Diag::ClangTidy)))));
}

TEST(DiagnosticsTest, ClangTidy) {
  Annotations Test(R"cpp(
    #include $deprecated[["assert.h"]]

    #define $macrodef[[SQUARE]](X) (X)*(X)
    int $main[[main]]() {
      int y = 4;
      return SQUARE($macroarg[[++]]y);
      return $doubled[[sizeof(sizeof(int))]];
    }

    // misc-no-recursion uses a custom traversal from the TUDecl
    void foo();
    void $bar[[bar]]() {
      foo();
    }
    void $foo[[foo]]() {
      bar();
    }
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  TU.HeaderFilename = "assert.h"; // Suppress "not found" error.
  TU.ClangTidyProvider = addTidyChecks("bugprone-sizeof-expression,"
                                       "bugprone-macro-repeated-side-effects,"
                                       "modernize-deprecated-headers,"
                                       "modernize-use-trailing-return-type,"
                                       "misc-no-recursion");
  TU.ExtraArgs.push_back("-Wno-unsequenced");
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(UnorderedElementsAre(
          AllOf(Diag(Test.range("deprecated"),
                     "inclusion of deprecated C++ header 'assert.h'; consider "
                     "using 'cassert' instead"),
                diagSource(Diag::ClangTidy),
                diagName("modernize-deprecated-headers"),
                withFix(Fix(Test.range("deprecated"), "<cassert>",
                            "change '\"assert.h\"' to '<cassert>'"))),
          Diag(Test.range("doubled"),
               "suspicious usage of 'sizeof(sizeof(...))'"),
          AllOf(Diag(Test.range("macroarg"),
                     "side effects in the 1st macro argument 'X' are "
                     "repeated in "
                     "macro expansion"),
                diagSource(Diag::ClangTidy),
                diagName("bugprone-macro-repeated-side-effects"),
                withNote(Diag(Test.range("macrodef"),
                              "macro 'SQUARE' defined here"))),
          AllOf(Diag(Test.range("main"),
                     "use a trailing return type for this function"),
                diagSource(Diag::ClangTidy),
                diagName("modernize-use-trailing-return-type"),
                // Verify there's no "[check-name]" suffix in the message.
                withFix(fixMessage(
                    "use a trailing return type for this function"))),
          Diag(Test.range("foo"),
               "function 'foo' is within a recursive call chain"),
          Diag(Test.range("bar"),
               "function 'bar' is within a recursive call chain"))));
}

TEST(DiagnosticsTest, ClangTidyEOF) {
  // clang-format off
  Annotations Test(R"cpp(
  [[#]]include <b.h>
  #include "a.h")cpp");
  // clang-format on
  auto TU = TestTU::withCode(Test.code());
  TU.ExtraArgs = {"-isystem."};
  TU.AdditionalFiles["a.h"] = TU.AdditionalFiles["b.h"] = "";
  TU.ClangTidyProvider = addTidyChecks("llvm-include-order");
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(Contains(
          AllOf(Diag(Test.range(), "#includes are not sorted properly"),
                diagSource(Diag::ClangTidy), diagName("llvm-include-order")))));
}

TEST(DiagnosticTest, TemplatesInHeaders) {
  // Diagnostics from templates defined in headers are placed at the expansion.
  Annotations Main(R"cpp(
    Derived<int> [[y]]; // error-ok
  )cpp");
  Annotations Header(R"cpp(
    template <typename T>
    struct Derived : [[T]] {};
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.HeaderCode = Header.code().str();
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(AllOf(
          Diag(Main.range(), "in template: base specifier must name a class"),
          withNote(Diag(Header.range(), "error occurred here"),
                   Diag(Main.range(), "in instantiation of template class "
                                      "'Derived<int>' requested here")))));
}

TEST(DiagnosticTest, MakeUnique) {
  // We usually miss diagnostics from header functions as we don't parse them.
  // std::make_unique is an exception.
  Annotations Main(R"cpp(
    struct S { S(char*); };
    auto x = std::[[make_unique]]<S>(42); // error-ok
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.HeaderCode = R"cpp(
    namespace std {
    // These mocks aren't quite right - we omit unique_ptr for simplicity.
    // forward is included to show its body is not needed to get the diagnostic.
    template <typename T> T&& forward(T& t);
    template <typename T, typename... A> T* make_unique(A&&... args) {
       return new T(std::forward<A>(args)...);
    }
    }
  )cpp";
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(
                  Diag(Main.range(),
                       "in template: "
                       "no matching constructor for initialization of 'S'")));
}

TEST(DiagnosticTest, CoroutineInHeader) {
  StringRef CoroutineH = R"cpp(
namespace std {
template <class Ret, typename... T>
struct coroutine_traits { using promise_type = typename Ret::promise_type; };

template <class Promise = void>
struct coroutine_handle {
  static coroutine_handle from_address(void *) noexcept;
  static coroutine_handle from_promise(Promise &promise);
  constexpr void* address() const noexcept;
};
template <>
struct coroutine_handle<void> {
  template <class PromiseType>
  coroutine_handle(coroutine_handle<PromiseType>) noexcept;
  static coroutine_handle from_address(void *);
  constexpr void* address() const noexcept;
};

struct awaitable {
  bool await_ready() noexcept { return false; }
  void await_suspend(coroutine_handle<>) noexcept {}
  void await_resume() noexcept {}
};
} // namespace std
  )cpp";

  StringRef Header = R"cpp(
#include "coroutine.h"
template <typename T> struct [[clang::coro_return_type]] Gen {
  struct promise_type {
    Gen<T> get_return_object() {
      return {};
    }
    std::awaitable  initial_suspend();
    std::awaitable  final_suspend() noexcept;
    void unhandled_exception();
    void return_value(T t);
  };
};

Gen<int> foo_coro(int b) { co_return b; }
  )cpp";
  Annotations Main(R"cpp(
// error-ok
#include "header.hpp"
Gen<int> $[[bar_coro]](int b) { return foo_coro(b); }
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles["coroutine.h"] = std::string(CoroutineH);
  TU.AdditionalFiles["header.hpp"] = std::string(Header);
  TU.ExtraArgs.push_back("--std=c++20");
  EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(hasRange(Main.range())));
}

TEST(DiagnosticTest, MakeShared) {
  // We usually miss diagnostics from header functions as we don't parse them.
  // std::make_shared is only parsed when --parse-forwarding-functions is set
  Annotations Main(R"cpp(
    struct S { S(char*); };
    auto x = std::[[make_shared]]<S>(42); // error-ok
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.HeaderCode = R"cpp(
    namespace std {
    // These mocks aren't quite right - we omit shared_ptr for simplicity.
    // forward is included to show its body is not needed to get the diagnostic.
    template <typename T> T&& forward(T& t);
    template <typename T, typename... A> T* make_shared(A&&... args) {
       return new T(std::forward<A>(args)...);
    }
    }
  )cpp";
  TU.ParseOpts.PreambleParseForwardingFunctions = true;
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(
                  Diag(Main.range(),
                       "in template: "
                       "no matching constructor for initialization of 'S'")));
}

TEST(DiagnosticTest, NoMultipleDiagnosticInFlight) {
  Annotations Main(R"cpp(
    template <typename T> struct Foo {
      T *begin();
      T *end();
    };
    struct LabelInfo {
      int a;
      bool b;
    };

    void f() {
      Foo<LabelInfo> label_info_map;
      [[for]] (auto it = label_info_map.begin(); it != label_info_map.end(); ++it) {
        auto S = *it;
      }
    }
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.ClangTidyProvider = addTidyChecks("modernize-loop-convert");
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
          Diag(Main.range(), "use range-based for loop instead"),
          diagSource(Diag::ClangTidy), diagName("modernize-loop-convert")))));
}

TEST(DiagnosticTest, RespectsDiagnosticConfig) {
  Annotations Main(R"cpp(
    // error-ok
    void x() {
      [[unknown]]();
      $ret[[return]] 42;
    }
  )cpp");
  auto TU = TestTU::withCode(Main.code());
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(Diag(Main.range(), "use of undeclared identifier 'unknown'"),
                  Diag(Main.range("ret"),
                       "void function 'x' should not return a value")));
  Config Cfg;
  Cfg.Diagnostics.Suppress.insert("return-type");
  WithContextValue WithCfg(Config::Key, std::move(Cfg));
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(Diag(Main.range(),
                               "use of undeclared identifier 'unknown'")));
}

TEST(DiagnosticTest, RespectsDiagnosticConfigInHeader) {
  Annotations Header(R"cpp(
    int x = "42";  // error-ok
  )cpp");
  Annotations Main(R"cpp(
    #include "header.hpp"
  )cpp");
  auto TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles["header.hpp"] = std::string(Header.code());
  Config Cfg;
  Cfg.Diagnostics.Suppress.insert("init_conversion_failed");
  WithContextValue WithCfg(Config::Key, std::move(Cfg));
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
}

TEST(DiagnosticTest, ClangTidySuppressionComment) {
  Annotations Main(R"cpp(
    int main() {
      int i = 3;
      double d = 8 / i;  // NOLINT
      // NOLINTNEXTLINE
      double e = 8 / i;
      #define BAD 8 / i
      double f = BAD;  // NOLINT
      double g = [[8]] / i;
      #define BAD2 BAD
      double h = BAD2;  // NOLINT
      // NOLINTBEGIN
      double x = BAD2;
      double y = BAD2;
      // NOLINTEND

      // verify no crashes on unmatched nolints.
      // NOLINTBEGIN
    }
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.ClangTidyProvider = addTidyChecks("bugprone-integer-division");
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
          Diag(Main.range(), "result of integer division used in a floating "
                             "point context; possible loss of precision"),
          diagSource(Diag::ClangTidy),
          diagName("bugprone-integer-division")))));
}

TEST(DiagnosticTest, ClangTidySystemMacro) {
  Annotations Main(R"cpp(
    #include "user.h"
    #include "system.h"
    int i = 3;
    double x = $inline[[8]] / i;
    double y = $user[[DIVIDE_USER]](i);
    double z = DIVIDE_SYS(i);
  )cpp");

  auto TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles["user.h"] = R"cpp(
    #define DIVIDE_USER(Y) 8/Y
  )cpp";
  TU.AdditionalFiles["system.h"] = R"cpp(
    #pragma clang system_header
    #define DIVIDE_SYS(Y) 8/Y
  )cpp";

  TU.ClangTidyProvider = addTidyChecks("bugprone-integer-division");
  std::string BadDivision = "result of integer division used in a floating "
                            "point context; possible loss of precision";

  // Expect to see warning from user macros, but not system macros.
  // This matches clang-tidy --system-headers=0 (the default).
  EXPECT_THAT(TU.build().getDiagnostics(),
              ifTidyChecks(
                  UnorderedElementsAre(Diag(Main.range("inline"), BadDivision),
                                       Diag(Main.range("user"), BadDivision))));
}

TEST(DiagnosticTest, ClangTidyWarningAsError) {
  Annotations Main(R"cpp(
    int main() {
      int i = 3;
      double f = [[8]] / i; // error-ok
    }
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.ClangTidyProvider =
      addTidyChecks("bugprone-integer-division", "bugprone-integer-division");
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(
          Diag(Main.range(), "result of integer division used in a floating "
                             "point context; possible loss of precision"),
          diagSource(Diag::ClangTidy), diagName("bugprone-integer-division"),
          diagSeverity(DiagnosticsEngine::Error)))));
}

TidyProvider addClangArgs(std::vector<llvm::StringRef> ExtraArgs,
                          llvm::StringRef Checks) {
  return [ExtraArgs = std::move(ExtraArgs), Checks = Checks.str()](
             tidy::ClangTidyOptions &Opts, llvm::StringRef) {
    if (!Opts.ExtraArgs)
      Opts.ExtraArgs.emplace();
    for (llvm::StringRef Arg : ExtraArgs)
      Opts.ExtraArgs->emplace_back(Arg);
    if (!Checks.empty())
      Opts.Checks = Checks;
  };
}

TEST(DiagnosticTest, ClangTidyEnablesClangWarning) {
  Annotations Main(R"cpp( // error-ok
    static void [[foo]]() {}
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  // This is always emitted as a clang warning, not a clang-tidy diagnostic.
  auto UnusedFooWarning =
      AllOf(Diag(Main.range(), "unused function 'foo'"),
            diagName("-Wunused-function"), diagSource(Diag::Clang),
            diagSeverity(DiagnosticsEngine::Warning));

  // Check the -Wunused warning isn't initially on.
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());

  // We enable warnings based on clang-tidy extra args, if the matching
  // clang-diagnostic- is there.
  TU.ClangTidyProvider =
      addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
  EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));

  // clang-diagnostic-* is acceptable
  TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "clang-diagnostic-*");
  EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));
  // And plain * (may turn on other checks too).
  TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "*");
  EXPECT_THAT(TU.build().getDiagnostics(), Contains(UnusedFooWarning));
  // And we can explicitly exclude a category too.
  TU.ClangTidyProvider = addClangArgs(
      {"-Wunused"}, "clang-diagnostic-*,-clang-diagnostic-unused-function");
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());

  // Without the exact check specified, the warnings are not enabled.
  TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "clang-diagnostic-unused");
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());

  // We don't respect other args.
  TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Dfoo=bar"},
                                      "clang-diagnostic-unused-function");
  EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning))
      << "Not unused function 'bar'!";

  // -Werror doesn't apply to warnings enabled by clang-tidy extra args.
  TU.ExtraArgs = {"-Werror"};
  TU.ClangTidyProvider =
      addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(diagSeverity(DiagnosticsEngine::Warning)));

  // But clang-tidy extra args won't *downgrade* errors to warnings either.
  TU.ExtraArgs = {"-Wunused", "-Werror"};
  TU.ClangTidyProvider =
      addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(diagSeverity(DiagnosticsEngine::Error)));

  // FIXME: we're erroneously downgrading the whole group, this should be Error.
  TU.ExtraArgs = {"-Wunused-function", "-Werror"};
  TU.ClangTidyProvider =
      addClangArgs({"-Wunused"}, "clang-diagnostic-unused-label");
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(diagSeverity(DiagnosticsEngine::Warning)));

  // This looks silly, but it's the typical result if a warning is enabled by a
  // high-level .clang-tidy file and disabled by a low-level one.
  TU.ExtraArgs = {};
  TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Wno-unused"},
                                      "clang-diagnostic-unused-function");
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());

  // Overriding only works in the proper order.
  TU.ClangTidyProvider =
      addClangArgs({"-Wunused"}, {"clang-diagnostic-unused-function"});
  EXPECT_THAT(TU.build().getDiagnostics(), SizeIs(1));

  // More specific vs less-specific: match clang behavior
  TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Wno-unused-function"},
                                      {"clang-diagnostic-unused-function"});
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
  TU.ClangTidyProvider = addClangArgs({"-Wunused-function", "-Wno-unused"},
                                      {"clang-diagnostic-unused-function"});
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());

  // We do allow clang-tidy config to disable warnings from the compile
  // command. It's unclear this is ideal, but it's hard to avoid.
  TU.ExtraArgs = {"-Wunused"};
  TU.ClangTidyProvider = addClangArgs({"-Wno-unused"}, {});
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
}

TEST(DiagnosticTest, LongFixMessages) {
  // We limit the size of printed code.
  Annotations Source(R"cpp(
    int main() {
      // error-ok
      int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;
      [[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;
    }
  )cpp");
  TestTU TU = TestTU::withCode(Source.code());
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(withFix(Fix(
          Source.range(),
          "somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",
          "change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "
          "'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));
  // Only show changes up to a first newline.
  Source = Annotations(R"cpp(
    // error-ok
    int main() {
      int ident;
      [[ide\
n]] = 10; // error-ok
    }
  )cpp");
  TU.Code = std::string(Source.code());
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(withFix(
                  Fix(Source.range(), "ident", "change 'ide\\…' to 'ident'"))));
}

TEST(DiagnosticTest, NewLineFixMessage) {
  Annotations Source("int a;[[]]");
  TestTU TU = TestTU::withCode(Source.code());
  TU.ExtraArgs = {"-Wnewline-eof"};
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(withFix((Fix(Source.range(), "\n", "insert '\\n'")))));
}

TEST(DiagnosticTest, ClangTidySuppressionCommentTrumpsWarningAsError) {
  Annotations Main(R"cpp(
    int main() {
      int i = 3;
      double f = [[8]] / i;  // NOLINT
    }
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.ClangTidyProvider =
      addTidyChecks("bugprone-integer-division", "bugprone-integer-division");
  EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
}

TEST(DiagnosticTest, ClangTidyNoLiteralDataInMacroToken) {
  Annotations Main(R"cpp(
    #define SIGTERM 15
    using pthread_t = int;
    int pthread_kill(pthread_t thread, int sig);
    int func() {
      pthread_t thread;
      return pthread_kill(thread, 0);
    }
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.ClangTidyProvider = addTidyChecks("bugprone-bad-signal-to-kill-thread");
  EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre()); // no-crash
}

TEST(DiagnosticTest, ElseAfterReturnRange) {
  Annotations Main(R"cpp(
    int foo(int cond) {
    if (cond == 1) {
      return 42;
    } [[else]] if (cond == 2) {
      return 43;
    }
    return 44;
    }
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.ClangTidyProvider = addTidyChecks("llvm-else-after-return");
  EXPECT_THAT(TU.build().getDiagnostics(),
              ifTidyChecks(ElementsAre(
                  Diag(Main.range(), "do not use 'else' after 'return'"))));
}

TEST(DiagnosticTest, ClangTidySelfContainedDiags) {
  Annotations Main(R"cpp($MathHeader[[]]
    struct Foo{
      int A, B;
      Foo()$Fix[[]] {
        $A[[A = 1;]]
        $B[[B = 1;]]
      }
    };
    void InitVariables() {
      float $C[[C]]$CFix[[]];
      double $D[[D]]$DFix[[]];
    }
  )cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.ClangTidyProvider =
      addTidyChecks("cppcoreguidelines-prefer-member-initializer,"
                    "cppcoreguidelines-init-variables");
  clangd::Fix ExpectedAFix;
  ExpectedAFix.Message =
      "'A' should be initialized in a member initializer of the constructor";
  ExpectedAFix.Edits.push_back(TextEdit{Main.range("Fix"), " : A(1)"});
  ExpectedAFix.Edits.push_back(TextEdit{Main.range("A"), ""});

  // When invoking clang-tidy normally, this code would produce `, B(1)` as the
  // fix the `B` member, as it would think its already included the ` : ` from
  // the previous `A` fix.
  clangd::Fix ExpectedBFix;
  ExpectedBFix.Message =
      "'B' should be initialized in a member initializer of the constructor";
  ExpectedBFix.Edits.push_back(TextEdit{Main.range("Fix"), " : B(1)"});
  ExpectedBFix.Edits.push_back(TextEdit{Main.range("B"), ""});

  clangd::Fix ExpectedCFix;
  ExpectedCFix.Message = "variable 'C' is not initialized";
  ExpectedCFix.Edits.push_back(TextEdit{Main.range("CFix"), " = NAN"});
  ExpectedCFix.Edits.push_back(
      TextEdit{Main.range("MathHeader"), "#include <math.h>\n\n"});

  // Again in clang-tidy only the include directive would be emitted for the
  // first warning. However we need the include attaching for both warnings.
  clangd::Fix ExpectedDFix;
  ExpectedDFix.Message = "variable 'D' is not initialized";
  ExpectedDFix.Edits.push_back(TextEdit{Main.range("DFix"), " = NAN"});
  ExpectedDFix.Edits.push_back(
      TextEdit{Main.range("MathHeader"), "#include <math.h>\n\n"});
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(UnorderedElementsAre(
          AllOf(Diag(Main.range("A"), "'A' should be initialized in a member "
                                      "initializer of the constructor"),
                withFix(equalToFix(ExpectedAFix))),
          AllOf(Diag(Main.range("B"), "'B' should be initialized in a member "
                                      "initializer of the constructor"),
                withFix(equalToFix(ExpectedBFix))),
          AllOf(Diag(Main.range("C"), "variable 'C' is not initialized"),
                withFix(equalToFix(ExpectedCFix))),
          AllOf(Diag(Main.range("D"), "variable 'D' is not initialized"),
                withFix(equalToFix(ExpectedDFix))))));
}

TEST(DiagnosticsTest, Preprocessor) {
  // This looks like a preamble, but there's an #else in the middle!
  // Check that:
  //  - the #else doesn't generate diagnostics (we had this bug)
  //  - we get diagnostics from the taken branch
  //  - we get no diagnostics from the not taken branch
  Annotations Test(R"cpp(
    #ifndef FOO
    #define FOO
      int a = [[b]]; // error-ok
    #else
      int x = y;
    #endif
    )cpp");
  EXPECT_THAT(
      TestTU::withCode(Test.code()).build().getDiagnostics(),
      ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
}

TEST(DiagnosticsTest, IgnoreVerify) {
  auto TU = TestTU::withCode(R"cpp(
    int a; // expected-error {{}}
  )cpp");
  TU.ExtraArgs.push_back("-Xclang");
  TU.ExtraArgs.push_back("-verify");
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
}

TEST(DiagnosticTest, IgnoreBEFilelistOptions) {
  auto TU = TestTU::withCode("");
  TU.ExtraArgs.push_back("-Xclang");
  for (const auto *DisableOption :
       {"-fsanitize-ignorelist=null", "-fprofile-list=null",
        "-fxray-always-instrument=null", "-fxray-never-instrument=null",
        "-fxray-attr-list=null"}) {
    TU.ExtraArgs.push_back(DisableOption);
    EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
    TU.ExtraArgs.pop_back();
  }
}

// Recursive main-file include is diagnosed, and doesn't crash.
TEST(DiagnosticsTest, RecursivePreamble) {
  auto TU = TestTU::withCode(R"cpp(
    #include "foo.h" // error-ok
    int symbol;
  )cpp");
  TU.Filename = "foo.h";
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(diagName("pp_including_mainfile_in_preamble")));
  EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
}

// Recursive main-file include with #pragma once guard is OK.
TEST(DiagnosticsTest, RecursivePreamblePragmaOnce) {
  auto TU = TestTU::withCode(R"cpp(
    #pragma once
    #include "foo.h"
    int symbol;
  )cpp");
  TU.Filename = "foo.h";
  EXPECT_THAT(TU.build().getDiagnostics(),
              Not(Contains(diagName("pp_including_mainfile_in_preamble"))));
  EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
}

// Recursive main-file include with #ifndef guard should be OK.
// However, it's not yet recognized (incomplete at end of preamble).
TEST(DiagnosticsTest, RecursivePreambleIfndefGuard) {
  auto TU = TestTU::withCode(R"cpp(
    #ifndef FOO
    #define FOO
    #include "foo.h" // error-ok
    int symbol;
    #endif
  )cpp");
  TU.Filename = "foo.h";
  // FIXME: should be no errors here.
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(diagName("pp_including_mainfile_in_preamble")));
  EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));
}

TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) {
  auto TU = TestTU::withCode(R"cpp(
#pragma clang assume_nonnull begin
void foo(int *x);
#pragma clang assume_nonnull end
)cpp");
  auto AST = TU.build();
  EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
  const auto *X = cast<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);
  ASSERT_TRUE(X->getOriginalType()->getNullability() ==
              NullabilityKind::NonNull);
}

TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) {
  Annotations Header(R"cpp(
#pragma clang assume_nonnull begin  // error-ok
void foo(int *X);
)cpp");
  auto TU = TestTU::withCode(R"cpp(
#include "foo.h"  // unterminated assume_nonnull should not affect bar.
void bar(int *Y);
)cpp");
  TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}};
  auto AST = TU.build();
  EXPECT_THAT(AST.getDiagnostics(),
              ElementsAre(diagName("pp_eof_in_assume_nonnull")));
  const auto *X = cast<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);
  ASSERT_TRUE(X->getOriginalType()->getNullability() ==
              NullabilityKind::NonNull);
  const auto *Y = cast<FunctionDecl>(findDecl(AST, "bar")).getParamDecl(0);
  ASSERT_FALSE(Y->getOriginalType()->getNullability());
}

TEST(DiagnosticsTest, InsideMacros) {
  Annotations Test(R"cpp(
    #define TEN 10
    #define RET(x) return x + 10

    int* foo() {
      RET($foo[[0]]); // error-ok
    }
    int* bar() {
      return $bar[[TEN]];
    }
    )cpp");
  EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(),
              ElementsAre(Diag(Test.range("foo"),
                               "cannot initialize return object of type "
                               "'int *' with an rvalue of type 'int'"),
                          Diag(Test.range("bar"),
                               "cannot initialize return object of type "
                               "'int *' with an rvalue of type 'int'")));
}

TEST(DiagnosticsTest, NoFixItInMacro) {
  Annotations Test(R"cpp(
    #define Define(name) void name() {}

    [[Define]](main) // error-ok
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
                                Not(withFix(_)))));
}

TEST(DiagnosticsTest, PragmaSystemHeader) {
  Annotations Test("#pragma clang [[system_header]]\n");
  auto TU = TestTU::withCode(Test.code());
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(AllOf(
          Diag(Test.range(), "#pragma system_header ignored in main file"))));
  TU.Filename = "TestTU.h";
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
}

TEST(ClangdTest, MSAsm) {
  // Parsing MS assembly tries to use the target MCAsmInfo, which we don't link.
  // We used to crash here. Now clang emits a diagnostic, which we filter out.
  llvm::InitializeAllTargetInfos(); // As in ClangdMain
  auto TU = TestTU::withCode("void fn() { __asm { cmp cl,64 } }");
  TU.ExtraArgs = {"-fms-extensions"};
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
}

TEST(DiagnosticsTest, ToLSP) {
  URIForFile MainFile =
      URIForFile::canonicalize(testPath("foo/bar/main.cpp"), "");
  URIForFile HeaderFile =
      URIForFile::canonicalize(testPath("foo/bar/header.h"), "");

  clangd::Diag D;
  D.ID = clang::diag::err_undeclared_var_use;
  D.Tags = {DiagnosticTag::Unnecessary};
  D.Name = "undeclared_var_use";
  D.Source = clangd::Diag::Clang;
  D.Message = "something terrible happened";
  D.Range = {pos(1, 2), pos(3, 4)};
  D.InsideMainFile = true;
  D.Severity = DiagnosticsEngine::Error;
  D.File = "foo/bar/main.cpp";
  D.AbsFile = std::string(MainFile.file());
  D.OpaqueData["test"] = "bar";

  clangd::Note NoteInMain;
  NoteInMain.Message = "declared somewhere in the main file";
  NoteInMain.Range = {pos(5, 6), pos(7, 8)};
  NoteInMain.Severity = DiagnosticsEngine::Remark;
  NoteInMain.File = "../foo/bar/main.cpp";
  NoteInMain.InsideMainFile = true;
  NoteInMain.AbsFile = std::string(MainFile.file());

  D.Notes.push_back(NoteInMain);

  clangd::Note NoteInHeader;
  NoteInHeader.Message = "declared somewhere in the header file";
  NoteInHeader.Range = {pos(9, 10), pos(11, 12)};
  NoteInHeader.Severity = DiagnosticsEngine::Note;
  NoteInHeader.File = "../foo/baz/header.h";
  NoteInHeader.InsideMainFile = false;
  NoteInHeader.AbsFile = std::string(HeaderFile.file());
  D.Notes.push_back(NoteInHeader);

  clangd::Fix F;
  F.Message = "do something";
  D.Fixes.push_back(F);

  // Diagnostics should turn into these:
  clangd::Diagnostic MainLSP;
  MainLSP.range = D.Range;
  MainLSP.severity = getSeverity(DiagnosticsEngine::Error);
  MainLSP.code = "undeclared_var_use";
  MainLSP.source = "clang";
  MainLSP.message =
      R"(Something terrible happened (fix available)

main.cpp:6:7: remark: declared somewhere in the main file

../foo/baz/header.h:10:11:
note: declared somewhere in the header file)";
  MainLSP.tags = {DiagnosticTag::Unnecessary};
  MainLSP.data = D.OpaqueData;

  clangd::Diagnostic NoteInMainLSP;
  NoteInMainLSP.range = NoteInMain.Range;
  NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark);
  NoteInMainLSP.message = R"(Declared somewhere in the main file

main.cpp:2:3: error: something terrible happened)";

  ClangdDiagnosticOptions Opts;
  // Transform diagnostics and check the results.
  std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
  toLSPDiags(D, MainFile, Opts,
             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
               LSPDiags.push_back(
                   {std::move(LSPDiag),
                    std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
             });

  EXPECT_THAT(
      LSPDiags,
      ElementsAre(Pair(equalToLSPDiag(MainLSP), ElementsAre(equalToFix(F))),
                  Pair(equalToLSPDiag(NoteInMainLSP), IsEmpty())));
  EXPECT_EQ(LSPDiags[0].first.code, "undeclared_var_use");
  EXPECT_EQ(LSPDiags[0].first.source, "clang");
  EXPECT_EQ(LSPDiags[1].first.code, "");
  EXPECT_EQ(LSPDiags[1].first.source, "");

  // Same thing, but don't flatten notes into the main list.
  LSPDiags.clear();
  Opts.EmitRelatedLocations = true;
  toLSPDiags(D, MainFile, Opts,
             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
               LSPDiags.push_back(
                   {std::move(LSPDiag),
                    std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
             });
  MainLSP.message = "Something terrible happened (fix available)";
  DiagnosticRelatedInformation NoteInMainDRI;
  NoteInMainDRI.message = "Declared somewhere in the main file";
  NoteInMainDRI.location.range = NoteInMain.Range;
  NoteInMainDRI.location.uri = MainFile;
  MainLSP.relatedInformation = {NoteInMainDRI};
  DiagnosticRelatedInformation NoteInHeaderDRI;
  NoteInHeaderDRI.message = "Declared somewhere in the header file";
  NoteInHeaderDRI.location.range = NoteInHeader.Range;
  NoteInHeaderDRI.location.uri = HeaderFile;
  MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};
  EXPECT_THAT(LSPDiags, ElementsAre(Pair(equalToLSPDiag(MainLSP),
                                         ElementsAre(equalToFix(F)))));
}

struct SymbolWithHeader {
  std::string QName;
  std::string DeclaringFile;
  std::string IncludeHeader;
};

std::unique_ptr<SymbolIndex>
buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {
  SymbolSlab::Builder Slab;
  for (const auto &S : Syms) {
    Symbol Sym = cls(S.QName);
    Sym.Flags |= Symbol::IndexedForCodeCompletion;
    Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();
    Sym.Definition.FileURI = S.DeclaringFile.c_str();
    Sym.IncludeHeaders.emplace_back(S.IncludeHeader, 1, Symbol::Include);
    Slab.insert(Sym);
  }
  return MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
}

TEST(IncludeFixerTest, IncompleteType) {
  auto TU = TestTU::withHeaderCode("namespace ns { class X; } ns::X *x;");
  TU.ExtraArgs.push_back("-std=c++20");
  auto Index = buildIndexWithSymbol(
      {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}});
  TU.ExternalIndex = Index.get();

  std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
      {"incomplete_nested_name_spec", "[[ns::X::]]Nested n;"},
      {"incomplete_base_class", "class Y : [[ns::X]] {};"},
      {"incomplete_member_access", "auto i = x[[->]]f();"},
      {"incomplete_type", "auto& [[[]]m] = *x;"},
      {"init_incomplete_type",
       "struct C { static int f(ns::X&); }; int i = C::f([[{]]});"},
      {"bad_cast_incomplete", "auto a = [[static_cast]]<ns::X>(0);"},
      {"template_nontype_parm_incomplete", "template <ns::X [[foo]]> int a;"},
      {"typecheck_decl_incomplete_type", "ns::X [[var]];"},
      {"typecheck_incomplete_tag", "auto i = [[(*x)]]->f();"},
      {"typecheck_nonviable_condition_incomplete",
       "struct A { operator ns::X(); } a; const ns::X &[[b]] = a;"},
      {"invalid_incomplete_type_use", "auto var = [[ns::X()]];"},
      {"sizeof_alignof_incomplete_or_sizeless_type",
       "auto s = [[sizeof]](ns::X);"},
      {"for_range_incomplete_type", "void foo() { for (auto i : [[*]]x ) {} }"},
      {"func_def_incomplete_result", "ns::X [[func]] () {}"},
      {"field_incomplete_or_sizeless", "class M { ns::X [[member]]; };"},
      {"array_incomplete_or_sizeless_type", "auto s = [[(ns::X[]){}]];"},
      {"call_incomplete_return", "ns::X f(); auto fp = &f; auto z = [[fp()]];"},
      {"call_function_incomplete_return", "ns::X foo(); auto a = [[foo()]];"},
      {"call_incomplete_argument", "int m(ns::X); int i = m([[*x]]);"},
      {"switch_incomplete_class_type", "void a() { [[switch]](*x) {} }"},
      {"delete_incomplete_class_type", "void f() { [[delete]] *x; }"},
      {"-Wdelete-incomplete", "void f() { [[delete]] x; }"},
      {"dereference_incomplete_type",
       R"cpp(void f() { asm("" : "=r"([[*]]x)::); })cpp"},
  };
  for (auto Case : Tests) {
    Annotations Main(Case.second);
    TU.Code = Main.code().str() + "\n // error-ok";
    EXPECT_THAT(
        TU.build().getDiagnostics(),
        ElementsAre(AllOf(diagName(Case.first), hasRange(Main.range()),
                          withFix(Fix(Range{}, "#include \"x.h\"\n",
                                      "Include \"x.h\" for symbol ns::X")))))
        << Case.second;
  }
}

TEST(IncludeFixerTest, IncompleteEnum) {
  Symbol Sym = enm("X");
  Sym.Flags |= Symbol::IndexedForCodeCompletion;
  Sym.CanonicalDeclaration.FileURI = Sym.Definition.FileURI = "unittest:///x.h";
  Sym.IncludeHeaders.emplace_back("\"x.h\"", 1, Symbol::Include);
  SymbolSlab::Builder Slab;
  Slab.insert(Sym);
  auto Index =
      MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());

  TestTU TU;
  TU.ExternalIndex = Index.get();
  TU.ExtraArgs.push_back("-std=c++20");
  TU.ExtraArgs.push_back("-fno-ms-compatibility"); // else incomplete enum is OK

  std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{
      {"incomplete_enum", "enum class X : int; using enum [[X]];"},
      {"underlying_type_of_incomplete_enum",
       "[[__underlying_type]](enum X) i;"},
  };
  for (auto Case : Tests) {
    Annotations Main(Case.second);
    TU.Code = Main.code().str() + "\n // error-ok";
    EXPECT_THAT(TU.build().getDiagnostics(),
                Contains(AllOf(diagName(Case.first), hasRange(Main.range()),
                               withFix(Fix(Range{}, "#include \"x.h\"\n",
                                           "Include \"x.h\" for symbol X")))))
        << Case.second;
  }
}

TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {
  Annotations Test(R"cpp(// error-ok
$insert[[]]namespace ns {
  class X;
}
class Y : $base[[public ns::X]] {};
int main() {
  ns::X *x;
  x$access[[->]]f();
}
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  Symbol Sym = cls("ns::X");
  Sym.Flags |= Symbol::IndexedForCodeCompletion;
  Sym.CanonicalDeclaration.FileURI = "unittest:///x.h";
  Sym.Definition.FileURI = "unittest:///x.cc";
  Sym.IncludeHeaders.emplace_back("\"x.h\"", 1, Symbol::Include);

  SymbolSlab::Builder Slab;
  Slab.insert(Sym);
  auto Index =
      MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(
                  Diag(Test.range("base"), "base class has incomplete type"),
                  Diag(Test.range("access"),
                       "member access into incomplete type 'ns::X'")));
}

TEST(IncludeFixerTest, Typo) {
  Annotations Test(R"cpp(// error-ok
$insert[[]]namespace ns {
void foo() {
  $unqualified1[[X]] x;
  // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
  // considered the unresolved type.
  $unqualified2[[X]]::Nested n;
}
struct S : $base[[X]] {};
}
void bar() {
  ns::$qualified1[[X]] x; // ns:: is valid.
  ns::$qualified2[[X]](); // Error: no member in namespace

  ::$global[[Global]] glob;
}
using Type = ns::$template[[Foo]]<int>;
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  auto Index = buildIndexWithSymbol(
      {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""},
       SymbolWithHeader{"Global", "unittest:///global.h", "\"global.h\""},
       SymbolWithHeader{"ns::Foo", "unittest:///foo.h", "\"foo.h\""}});
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(
      TU.build().getDiagnostics(),
      UnorderedElementsAre(
          AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
                diagName("unknown_typename"),
                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Include \"x.h\" for symbol ns::X"))),
          Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
          AllOf(Diag(Test.range("qualified1"),
                     "no type named 'X' in namespace 'ns'"),
                diagName("typename_nested_not_found"),
                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Include \"x.h\" for symbol ns::X"))),
          AllOf(Diag(Test.range("qualified2"),
                     "no member named 'X' in namespace 'ns'"),
                diagName("no_member"),
                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Include \"x.h\" for symbol ns::X"))),
          AllOf(Diag(Test.range("global"),
                     "no type named 'Global' in the global namespace"),
                diagName("typename_nested_not_found"),
                withFix(Fix(Test.range("insert"), "#include \"global.h\"\n",
                            "Include \"global.h\" for symbol Global"))),
          AllOf(Diag(Test.range("template"),
                     "no template named 'Foo' in namespace 'ns'"),
                diagName("no_member_template"),
                withFix(Fix(Test.range("insert"), "#include \"foo.h\"\n",
                            "Include \"foo.h\" for symbol ns::Foo"))),
          AllOf(Diag(Test.range("base"), "expected class name"),
                diagName("expected_class_name"),
                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Include \"x.h\" for symbol ns::X")))));
}

TEST(IncludeFixerTest, TypoInMacro) {
  auto TU = TestTU::withCode(R"cpp(// error-ok
#define ID(T) T
X a1;
ID(X a2);
ns::X a3;
ID(ns::X a4);
namespace ns{};
ns::X a5;
ID(ns::X a6);
)cpp");
  auto Index = buildIndexWithSymbol(
      {SymbolWithHeader{"X", "unittest:///x.h", "\"x.h\""},
       SymbolWithHeader{"ns::X", "unittest:///ns.h", "\"x.h\""}});
  TU.ExternalIndex = Index.get();
  // FIXME: -fms-compatibility (which is default on windows) breaks the
  // ns::X cases when the namespace is undeclared. Find out why!
  TU.ExtraArgs = {"-fno-ms-compatibility"};
  EXPECT_THAT(TU.build().getDiagnostics(), Each(withFix(_)));
}

TEST(IncludeFixerTest, MultipleMatchedSymbols) {
  Annotations Test(R"cpp(// error-ok
$insert[[]]namespace na {
namespace nb {
void foo() {
  $unqualified[[X]] x;
}
}
}
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  auto Index = buildIndexWithSymbol(
      {SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""},
       SymbolWithHeader{"na::nb::X", "unittest:///b.h", "\"b.h\""}});
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(AllOf(
                  Diag(Test.range("unqualified"), "unknown type name 'X'"),
                  diagName("unknown_typename"),
                  withFix(Fix(Test.range("insert"), "#include \"a.h\"\n",
                              "Include \"a.h\" for symbol na::X"),
                          Fix(Test.range("insert"), "#include \"b.h\"\n",
                              "Include \"b.h\" for symbol na::nb::X")))));
}

TEST(IncludeFixerTest, NoCrashMemberAccess) {
  Annotations Test(R"cpp(// error-ok
    struct X { int  xyz; };
    void g() { X x; x.$[[xy]]; }
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  auto Index = buildIndexWithSymbol(
      SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""});
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(
      TU.build().getDiagnostics(),
      UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
}

TEST(IncludeFixerTest, UseCachedIndexResults) {
  // As index results for the identical request are cached, more than 5 fixes
  // are generated.
  Annotations Test(R"cpp(// error-ok
$insert[[]]void foo() {
  $x1[[X]] x;
  $x2[[X]] x;
  $x3[[X]] x;
  $x4[[X]] x;
  $x5[[X]] x;
  $x6[[X]] x;
  $x7[[X]] x;
}

class X;
void bar(X *x) {
  x$a1[[->]]f();
  x$a2[[->]]f();
  x$a3[[->]]f();
  x$a4[[->]]f();
  x$a5[[->]]f();
  x$a6[[->]]f();
  x$a7[[->]]f();
}
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  auto Index =
      buildIndexWithSymbol(SymbolWithHeader{"X", "unittest:///a.h", "\"a.h\""});
  TU.ExternalIndex = Index.get();

  auto Parsed = TU.build();
  for (const auto &D : Parsed.getDiagnostics()) {
    if (D.Fixes.size() != 1) {
      ADD_FAILURE() << "D.Fixes.size() != 1";
      continue;
    }
    EXPECT_EQ(D.Fixes[0].Message, std::string("Include \"a.h\" for symbol X"));
  }
}

TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
  Annotations Test(R"cpp(// error-ok
$insert[[]]namespace ns {
}
void g() {  ns::$[[scope]]::X_Y();  }
  )cpp");
  TestTU TU;
  TU.Code = std::string(Test.code());
  // FIXME: Figure out why this is needed and remove it, PR43662.
  TU.ExtraArgs.push_back("-fno-ms-compatibility");
  auto Index = buildIndexWithSymbol(
      SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(
      TU.build().getDiagnostics(),
      UnorderedElementsAre(
          AllOf(Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
                diagName("no_member"),
                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Include \"x.h\" for symbol ns::scope::X_Y")))));
}

TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
  Annotations Test(R"cpp(// error-ok
$insert[[]]namespace clang {
void f() {
  // "clangd::" will be corrected to "clang::" by Sema.
  $q1[[clangd]]::$x[[X]] x;
  $q2[[clangd]]::$ns[[ns]]::Y y;
}
}
  )cpp");
  TestTU TU;
  TU.Code = std::string(Test.code());
  // FIXME: Figure out why this is needed and remove it, PR43662.
  TU.ExtraArgs.push_back("-fno-ms-compatibility");
  auto Index = buildIndexWithSymbol(
      {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
       SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(
      TU.build().getDiagnostics(),
      UnorderedElementsAre(
          AllOf(Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
                                       "did you mean 'clang'?"),
                diagName("undeclared_var_use_suggest"),
                withFix(_, // change clangd to clang
                        Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Include \"x.h\" for symbol clang::clangd::X"))),
          AllOf(Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
                diagName("typename_nested_not_found"),
                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                            "Include \"x.h\" for symbol clang::clangd::X"))),
          AllOf(
              Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
                                     "did you mean 'clang'?"),
              diagName("undeclared_var_use_suggest"),
              withFix(_, // change clangd to clang
                      Fix(Test.range("insert"), "#include \"y.h\"\n",
                          "Include \"y.h\" for symbol clang::clangd::ns::Y"))),
          AllOf(Diag(Test.range("ns"),
                     "no member named 'ns' in namespace 'clang'"),
                diagName("no_member"),
                withFix(
                    Fix(Test.range("insert"), "#include \"y.h\"\n",
                        "Include \"y.h\" for symbol clang::clangd::ns::Y")))));
}

TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
  Annotations Test(R"cpp(// error-ok
$insert[[]]namespace a {}
namespace b = a;
namespace c {
  b::$[[X]] x;
}
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  auto Index = buildIndexWithSymbol(
      SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(AllOf(
                  Diag(Test.range(), "no type named 'X' in namespace 'a'"),
                  diagName("typename_nested_not_found"),
                  withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
                              "Include \"x.h\" for symbol a::X")))));
}

TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
  Annotations Test(R"cpp(
    template <typename T> struct Templ {
      template <typename U>
      typename U::type operator=(const U &);
    };

    struct A {
      Templ<char> s;
      A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.
    };
  )cpp");

  auto TU = TestTU::withCode(Test.code());
  auto Index = buildIndexWithSymbol({});
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(Diag(Test.range(), "use of undeclared identifier 'a'")));
}

TEST(IncludeFixerTest, HeaderNamedInDiag) {
  Annotations Test(R"cpp(
    $insert[[]]int main() {
      [[printf]]("");
    }
  )cpp");
  auto TU = TestTU::withCode(Test.code());
  TU.ExtraArgs = {"-xc", "-std=c99",
                  "-Wno-error=implicit-function-declaration"};
  auto Index = buildIndexWithSymbol({});
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(AllOf(
          Diag(Test.range(), "call to undeclared library function 'printf' "
                             "with type 'int (const char *, ...)'; ISO C99 "
                             "and later do not support implicit function "
                             "declarations"),
          withFix(Fix(Test.range("insert"), "#include <stdio.h>\n",
                      "Include <stdio.h> for symbol printf")))));

  TU.ExtraArgs = {"-xc", "-std=c89"};
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(AllOf(
          Diag(Test.range(), "implicitly declaring library function 'printf' "
                             "with type 'int (const char *, ...)'"),
          withFix(Fix(Test.range("insert"), "#include <stdio.h>\n",
                      "Include <stdio.h> for symbol printf")))));
}

TEST(IncludeFixerTest, CImplicitFunctionDecl) {
  Annotations Test("void x() { [[foo]](); }");
  auto TU = TestTU::withCode(Test.code());
  TU.Filename = "test.c";
  TU.ExtraArgs = {"-std=c99", "-Wno-error=implicit-function-declaration"};

  Symbol Sym = func("foo");
  Sym.Flags |= Symbol::IndexedForCodeCompletion;
  Sym.CanonicalDeclaration.FileURI = "unittest:///foo.h";
  Sym.IncludeHeaders.emplace_back("\"foo.h\"", 1, Symbol::Include);

  SymbolSlab::Builder Slab;
  Slab.insert(Sym);
  auto Index =
      MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
  TU.ExternalIndex = Index.get();

  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ElementsAre(AllOf(
          Diag(Test.range(),
               "call to undeclared function 'foo'; ISO C99 and later do not "
               "support implicit function declarations"),
          withFix(Fix(Range{}, "#include \"foo.h\"\n",
                      "Include \"foo.h\" for symbol foo")))));

  TU.ExtraArgs = {"-std=c89", "-Wall"};
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(AllOf(
                  Diag(Test.range(), "implicit declaration of function 'foo'"),
                  withFix(Fix(Range{}, "#include \"foo.h\"\n",
                              "Include \"foo.h\" for symbol foo")))));
}

TEST(DiagsInHeaders, DiagInsideHeader) {
  Annotations Main(R"cpp(
    #include [["a.h"]]
    void foo() {})cpp");
  Annotations Header("[[no_type_spec]]; // error-ok");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(AllOf(
                  Diag(Main.range(), "in included file: a type specifier is "
                                     "required for all declarations"),
                  withNote(Diag(Header.range(), "error occurred here")))));
}

TEST(DiagsInHeaders, DiagInTransitiveInclude) {
  Annotations Main(R"cpp(
    #include [["a.h"]]
    void foo() {})cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", "#include \"b.h\""},
                        {"b.h", "no_type_spec; // error-ok"}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(Diag(Main.range(),
                                        "in included file: a type specifier is "
                                        "required for all declarations")));
}

TEST(DiagsInHeaders, DiagInMultipleHeaders) {
  Annotations Main(R"cpp(
    #include $a[["a.h"]]
    #include $b[["b.h"]]
    void foo() {})cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", "no_type_spec; // error-ok"},
                        {"b.h", "no_type_spec; // error-ok"}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(
                  Diag(Main.range("a"), "in included file: a type specifier is "
                                        "required for all declarations"),
                  Diag(Main.range("b"), "in included file: a type specifier is "
                                        "required for all declarations")));
}

TEST(DiagsInHeaders, PreferExpansionLocation) {
  Annotations Main(R"cpp(
    #include [["a.h"]]
    #include "b.h"
    void foo() {})cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {
      {"a.h", "#include \"b.h\"\n"},
      {"b.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              Contains(Diag(Main.range(), "in included file: a type specifier "
                                          "is required for all declarations")));
}

TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
  Annotations Main(R"cpp(
    #define X
    #include "a.h"
    #undef X
    #include [["b.h"]]
    void foo() {})cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {
      {"a.h", "#include \"c.h\"\n"},
      {"b.h", "#include \"c.h\"\n"},
      {"c.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(Diag(Main.range(),
                                        "in included file: a type specifier is "
                                        "required for all declarations")));
}

TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
  Annotations Main(R"cpp(
    #include [["a.h"]]
    #include "b.h"
    void foo() {})cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", "#include \"c.h\"\n"},
                        {"b.h", "#include \"c.h\"\n"},
                        {"c.h", R"cpp(
      #ifndef X
      #define X
      no_type_spec_0; // error-ok
      no_type_spec_1;
      no_type_spec_2;
      no_type_spec_3;
      no_type_spec_4;
      no_type_spec_5;
      no_type_spec_6;
      no_type_spec_7;
      no_type_spec_8;
      no_type_spec_9;
      no_type_spec_10;
      #endif)cpp"}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(Diag(Main.range(),
                                        "in included file: a type specifier is "
                                        "required for all declarations")));
}

TEST(DiagsInHeaders, OnlyErrorOrFatal) {
  Annotations Main(R"cpp(
    #include [["a.h"]]
    void foo() {})cpp");
  Annotations Header(R"cpp(
    [[no_type_spec]]; // error-ok
    int x = 5/0;)cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(AllOf(
                  Diag(Main.range(), "in included file: a type specifier is "
                                     "required for all declarations"),
                  withNote(Diag(Header.range(), "error occurred here")))));
}

TEST(DiagsInHeaders, OnlyDefaultErrorOrFatal) {
  Annotations Main(R"cpp(
    #include [["a.h"]] // get unused "foo" warning when building preamble.
    )cpp");
  Annotations Header(R"cpp(
    namespace { void foo() {} }
    void func() {foo();} ;)cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
  // promote warnings to errors.
  TU.ExtraArgs = {"-Werror", "-Wunused"};
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
}

TEST(DiagsInHeaders, FromNonWrittenSources) {
  Annotations Main(R"cpp(
    #include [["a.h"]]
    void foo() {})cpp");
  Annotations Header(R"cpp(
    int x = 5/0;
    int b = [[FOO]]; // error-ok)cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
  TU.ExtraArgs = {"-DFOO=NOOO"};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(AllOf(
                  Diag(Main.range(),
                       "in included file: use of undeclared identifier 'NOOO'"),
                  withNote(Diag(Header.range(), "error occurred here")))));
}

TEST(DiagsInHeaders, ErrorFromMacroExpansion) {
  Annotations Main(R"cpp(
  void bar() {
    int fo; // error-ok
    #include [["a.h"]]
  })cpp");
  Annotations Header(R"cpp(
  #define X foo
  X;)cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(
                  Diag(Main.range(), "in included file: use of undeclared "
                                     "identifier 'foo'; did you mean 'fo'?")));
}

TEST(DiagsInHeaders, ErrorFromMacroArgument) {
  Annotations Main(R"cpp(
  void bar() {
    int fo; // error-ok
    #include [["a.h"]]
  })cpp");
  Annotations Header(R"cpp(
  #define X(arg) arg
  X(foo);)cpp");
  TestTU TU = TestTU::withCode(Main.code());
  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(
                  Diag(Main.range(), "in included file: use of undeclared "
                                     "identifier 'foo'; did you mean 'fo'?")));
}

TEST(IgnoreDiags, FromNonWrittenInclude) {
  TestTU TU;
  TU.ExtraArgs.push_back("--include=a.h");
  TU.AdditionalFiles = {{"a.h", "void main();"}};
  // The diagnostic "main must return int" is from the header, we don't attempt
  // to render it in the main file as there is no written location there.
  EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
}

TEST(ToLSPDiag, RangeIsInMain) {
  ClangdDiagnosticOptions Opts;
  clangd::Diag D;
  D.Range = {pos(1, 2), pos(3, 4)};
  D.Notes.emplace_back();
  Note &N = D.Notes.back();
  N.Range = {pos(2, 3), pos(3, 4)};

  D.InsideMainFile = true;
  N.InsideMainFile = false;
  toLSPDiags(D, {}, Opts,
             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
               EXPECT_EQ(LSPDiag.range, D.Range);
             });

  D.InsideMainFile = false;
  N.InsideMainFile = true;
  toLSPDiags(D, {}, Opts,
             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
               EXPECT_EQ(LSPDiag.range, N.Range);
             });
}

TEST(ParsedASTTest, ModuleSawDiag) {
  TestTU TU;

  auto AST = TU.build();
        #if 0
  EXPECT_THAT(AST.getDiagnostics(),
              testing::Contains(Diag(Code.range(), KDiagMsg.str())));
        #endif
}

TEST(Preamble, EndsOnNonEmptyLine) {
  TestTU TU;
  TU.ExtraArgs = {"-Wnewline-eof"};

  {
    TU.Code = "#define FOO\n  void bar();\n";
    auto AST = TU.build();
    EXPECT_THAT(AST.getDiagnostics(), IsEmpty());
  }
  {
    Annotations Code("#define FOO[[]]");
    TU.Code = Code.code().str();
    auto AST = TU.build();
    EXPECT_THAT(
        AST.getDiagnostics(),
        testing::Contains(Diag(Code.range(), "no newline at end of file")));
  }
}

TEST(Diagnostics, Tags) {
  TestTU TU;
  TU.ExtraArgs = {"-Wunused", "-Wdeprecated"};
  Annotations Test(R"cpp(
  void bar() __attribute__((deprecated));
  void foo() {
    int $unused[[x]];
    $deprecated[[bar]]();
  })cpp");
  TU.Code = Test.code().str();
  EXPECT_THAT(TU.build().getDiagnostics(),
              UnorderedElementsAre(
                  AllOf(Diag(Test.range("unused"), "unused variable 'x'"),
                        withTag(DiagnosticTag::Unnecessary)),
                  AllOf(Diag(Test.range("deprecated"), "'bar' is deprecated"),
                        withTag(DiagnosticTag::Deprecated))));

  Test = Annotations(R"cpp(
    $typedef[[typedef int INT]];
  )cpp");
  TU.Code = Test.code();
  TU.ClangTidyProvider = addTidyChecks("modernize-use-using");
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      ifTidyChecks(UnorderedElementsAre(
          AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),
                withTag(DiagnosticTag::Deprecated)))));
}

TEST(Diagnostics, DeprecatedDiagsAreHints) {
  ClangdDiagnosticOptions Opts;
  std::optional<clangd::Diagnostic> Diag;
  clangd::Diag D;
  D.Range = {pos(1, 2), pos(3, 4)};
  D.InsideMainFile = true;

  // Downgrade warnings with deprecated tags to remark.
  D.Tags = {Deprecated};
  D.Severity = DiagnosticsEngine::Warning;
  toLSPDiags(D, {}, Opts,
             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
               Diag = std::move(LSPDiag);
             });
  EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Remark));
  Diag.reset();

  // Preserve errors.
  D.Severity = DiagnosticsEngine::Error;
  toLSPDiags(D, {}, Opts,
             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
               Diag = std::move(LSPDiag);
             });
  EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Error));
  Diag.reset();

  // No-op without tag.
  D.Tags = {};
  D.Severity = DiagnosticsEngine::Warning;
  toLSPDiags(D, {}, Opts,
             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
               Diag = std::move(LSPDiag);
             });
  EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Warning));
}

TEST(DiagnosticsTest, IncludeCleaner) {
  Annotations Test(R"cpp(
$fix[[  $diag[[#include "unused.h"]]
]]
  #include "used.h"

  #include "ignore.h"

  #include <system_header.h>

  void foo() {
    used();
  }
  )cpp");
  TestTU TU;
  TU.Code = Test.code().str();
  TU.AdditionalFiles["unused.h"] = R"cpp(
    #pragma once
    void unused() {}
  )cpp";
  TU.AdditionalFiles["used.h"] = R"cpp(
    #pragma once
    void used() {}
  )cpp";
  TU.AdditionalFiles["ignore.h"] = R"cpp(
    #pragma once
    void ignore() {}
  )cpp";
  TU.AdditionalFiles["system/system_header.h"] = "";
  TU.ExtraArgs = {"-isystem" + testPath("system")};
  Config Cfg;
  Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;
  // Set filtering.
  Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back(
      [](llvm::StringRef Header) { return Header.ends_with("ignore.h"); });
  WithContextValue WithCfg(Config::Key, std::move(Cfg));
  auto AST = TU.build();
  EXPECT_THAT(
      AST.getDiagnostics(),
      Contains(AllOf(
          Diag(Test.range("diag"),
               "included header unused.h is not used directly"),
          withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd),
          withFix(Fix(Test.range("fix"), "", "remove #include directive")))));
  auto &Diag = AST.getDiagnostics().front();
  EXPECT_THAT(getDiagnosticDocURI(Diag.Source, Diag.ID, Diag.Name),
              llvm::ValueIs(Not(IsEmpty())));
  Cfg.Diagnostics.SuppressAll = true;
  WithContextValue SuppressAllWithCfg(Config::Key, std::move(Cfg));
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
  Cfg.Diagnostics.SuppressAll = false;
  Cfg.Diagnostics.Suppress = {"unused-includes"};
  WithContextValue SuppressFilterWithCfg(Config::Key, std::move(Cfg));
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
}

TEST(DiagnosticsTest, FixItFromHeader) {
  llvm::StringLiteral Header(R"cpp(
    void foo(int *);
    void foo(int *, int);)cpp");
  Annotations Source(R"cpp(
  /*error-ok*/
    void bar() {
      int x;
      $diag[[foo]]($fix[[]]x, 1);
    })cpp");
  TestTU TU;
  TU.Code = Source.code().str();
  TU.HeaderCode = Header.str();
  EXPECT_THAT(
      TU.build().getDiagnostics(),
      UnorderedElementsAre(AllOf(
          Diag(Source.range("diag"), "no matching function for call to 'foo'"),
          withFix(Fix(Source.range("fix"), "&",
                      "candidate function not viable: no known conversion from "
                      "'int' to 'int *' for 1st argument; take the address of "
                      "the argument with &")))));
}

TEST(DiagnosticsTest, UnusedInHeader) {
  // Clang diagnoses unused static inline functions outside headers.
  auto TU = TestTU::withCode("static inline void foo(void) {}");
  TU.ExtraArgs.push_back("-Wunused-function");
  TU.Filename = "test.c";
  EXPECT_THAT(TU.build().getDiagnostics(),
              ElementsAre(withID(diag::warn_unused_function)));
  // Sema should recognize a *.h file open in clangd as a header.
  // https://github.com/clangd/vscode-clangd/issues/360
  TU.Filename = "test.h";
  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
}

} // namespace
} // namespace clangd
} // namespace clang
